﻿using Microsoft.Scripting.Utils;
using Scala.Compiler.Ast;
using Scala.Compiler.Binding;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Scala.Compiler
{
    public class ScalaTypeNamer : ScalaWalker
    {
        private ScalaAst _globalAst;
        internal ScalaScope _currentScope;
        private List<ScalaScope> _scopes = new List<ScalaScope>();

        private ScalaTypeNamer(ScalaAst ast)
        {
            ContractUtils.RequiresNotNull(ast, "ast");

            _globalAst = ast;
        }

        #region Public Surface

        internal static void TypeNameAst(ScalaAst ast)
        {
            Assert.NotNull(ast);
            ScalaTypeNamer typer = new ScalaTypeNamer(ast);
            typer.TypeName(ast);
        }

        #endregion

        private void TypeName(ScalaAst unboundAst)
        {
            Assert.NotNull(unboundAst);

            unboundAst.Walk(this);


        }

        #region AstBinder Overrides

        public override bool Walk(PackageDefStatement node)
        {
            return true;
        }

        public override void PostWalk(PackageDefStatement node)
        {
            _currentScope = node.Scope.ParentScope;
            base.PostWalk(node);
        }

        public override bool Walk(ModuleDefStatement node)
        {
            // First Ensuring all the Variables are present in the Scope
            //foreach (var x in node.Scope.NameCheckList)
            //{
            //    if (!node.Scope.CheckName(x)) throw new Exception(String.Format("Undefined value {0} in the Scope {1}", x, node.Scope.Name));
            //}

            _currentScope = node.Scope;

            if (node.Scope.ParentScope.IsPackage)
            {
                _currentScope.CurrentType.TypeBuilder = node.GlobalParent.ModuleBuilder.DefineType(_currentScope.CurrentType.Name, _currentScope.CurrentType.GetAttributes());
            }
            else if (node.Scope.ParentScope.IsClass || node.Scope.ParentScope.IsModule)
            {
                if (node.Scope.ParentScope.CurrentType == null)
                {
                    throw new Exception("Parent Type of not found");
                }
                _currentScope.CurrentType.TypeBuilder = _currentScope.ParentScope.CurrentType.TypeBuilder.DefineNestedType(_currentScope.CurrentType.Name, _currentScope.CurrentType.GetAttributes());
            }

            node.Body.Walk(this);
            if (!(node.TypeName == node.Name))
            {
                throw new Exception("Module Definition node is not constructed corretly");
            }
            return false;
        }

        public override void PostWalk(ModuleDefStatement node)
        {
            _currentScope = node.Scope.ParentScope;
            base.PostWalk(node);
        }

        public override bool Walk(ClassDefStatement node)
        {
            // First Ensuring all the Variables are present in the Scope
            foreach (var x in node.Scope.NameCheckList)
            {
                if (!node.Scope.CheckName(x)) throw new Exception(String.Format("Undefined value {0} in the Scope {1}", x, node.Scope.Name));
            }

            _currentScope = node.Scope;

            if (node.Scope.ParentScope.IsPackage)
            {
                _currentScope.CurrentType.TypeBuilder = node.GlobalParent.ModuleBuilder.DefineType(_currentScope.CurrentType.Name, _currentScope.CurrentType.GetAttributes());
            }
            else if (node.Scope.ParentScope.IsClass || node.Scope.ParentScope.IsModule)
            {
                if (node.Scope.ParentScope.CurrentType == null)
                {
                    throw new Exception("Parent Type of not found");
                }
                _currentScope.CurrentType.TypeBuilder = _currentScope.ParentScope.CurrentType.TypeBuilder.DefineNestedType(_currentScope.CurrentType.Name, _currentScope.CurrentType.GetAttributes());
            }

            node.Body.Walk(this);
            if (!(node.TypeName == node.Name))
            {
                throw new Exception("Class Definition node is not constructed corretly");
            }
            return false;
        }

        public override void PostWalk(ClassDefStatement node)
        {
            _currentScope = node.Scope.ParentScope;
            base.PostWalk(node);
        }

        public override bool Walk(DefDefStatement node)
        {
            // First Ensuring all the Variables are present in the Scope
            foreach (var x in node.Scope.NameCheckList)
            {
                if (!node.Scope.CheckName(x)) throw new Exception(String.Format("Undefined value {0} in the Scope {1}", x, node.Scope.Name));
            }

            _currentScope = node.Scope;

            node.Body.Walk(this);
            if (node.TypeName == null)
            {
                node.TypeName = node.Body.TypeName;
            }
            return false;
        }

        public override void PostWalk(DefDefStatement node)
        {
            _currentScope = node.Scope.ParentScope;
            base.PostWalk(node);
        }

        public override bool Walk(ValDefStatement node)
        {
            _currentScope = node.Scope;

            node.Rhs.Walk(this);

            node.TypeName = node.Rhs.TypeName;

            PostWalk(node);
            return false;
        }

        public override void PostWalk(ValDefStatement node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(BlockStatement node)
        {
            if (node.Stats != null)
            {
                foreach (Statement s in node.Stats) { s.Walk(this); }
            }
            if (node.Expr != null) node.Expr.Walk(this);

            if (node.TypeName == null)
            {
                if (node.Expr is ConstantExpression) node.TypeName = node.Expr.TypeName;
                else if (node.Expr is NameExpression) node.TypeName = node.Expr.TypeName;
                else node.TypeName = "Unit";
            }
            if (node.TypeName == null)
            {
                throw new Exception("Unable to determine the type of the Block");
            }
            PostWalk(node);
            return false;
        }

        public override void PostWalk(BlockStatement node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(NameExpression node)
        {
            if (node.TypeName == null)
            {
                ScalaFunction func;
                ScalaType tp;
                node.Scope.TryGetAnyFunc(node.Name, out func);
                node.Scope.TryGetAnyType(node.Name, out tp);
                if(func != null) node.TypeName = func.DefStatement.TypeName;
                if (tp != null) node.TypeName = tp.Name;
            }
            
            return false;
        }

        public override void PostWalk(NameExpression node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(ExpressionStatement node)
        {
            node.TypeName = node.Expression.TypeName;
            if (node.TypeName == null)
            {
                throw new Exception("Unable to determine the type of expression");
            }
            return base.Walk(node);
        }

        public override void PostWalk(ExpressionStatement node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(ApplyExpression node)
        {
            _currentScope = node.Scope;

            if (node.Target != null)
            {
                node.Target.Walk(this);
            }
            if (node.Args != null)
            {
                foreach (Arg arg in node.Args)
                {
                    arg.Walk(this);
                }
            }
            node.TypeName = node.Target.TypeName;
            return false;
        }

        public override void PostWalk(ApplyExpression node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(Arg node)
        {
            _currentScope = node.Scope;
            node.TypeName = node.Expression.TypeName;
            return base.Walk(node);
        }

        public override void PostWalk(Arg node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(NewStatement node)
        {
            _currentScope = node.Scope;
            ScalaType t;
            if (!node.Scope.TryGetAnyType(node.TypeName, out t)) throw new Exception(String.Format("Not found the Type {0} in the Scope {1}", node.TypeName, node.Scope.Name));
            
            node.TargetScalaType = t;
            return base.Walk(node);
        }

        public override void PostWalk(NewStatement node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(SelectStatement node)
        {
            if (node.Qual is NameExpression)
            {
                ScalaType scType,retType;
                ScalaFunction func;
                ScalaVariable prop;

                if (node.Qual.TypeName == null)
                {
                    node.Qual.Walk(this);
                }

                node.Scope.TryGetAnyType(node.Qual.TypeName, out scType);

                scType.ScalaScope.TryGetFunc((node.ExprName as NameExpression).Name, out func);
                scType.ScalaScope.TryGetType((node.ExprName as NameExpression).Name, out retType);
                scType.ScalaScope.TryGetVariable((node.ExprName as NameExpression).Name, out prop);

                if (func != null)
                {
                    if (func.DefStatement.TypeName == null)
                    {
                        func.DefStatement.Walk(this);
                    }
                    node.TypeName = func.DefStatement.TypeName;
                    node.ExprName.TypeName = func.DefStatement.TypeName;
                }
                if (retType != null)
                {
                    node.TypeName = retType.Name;
                    node.ExprName.TypeName = retType.Name;
                }
                if (prop != null)
                {
                    if(prop.Ast.TypeName == null)
                    {
                        prop.Ast.Walk(this);
                    }
                    node.TypeName = prop.TypeName;
                    node.ExprName.TypeName = prop.TypeName;
                }
            }
            if (node.TypeName == null)
            {
                throw new Exception("Unable to determine the type of the variable");
            }
            return base.Walk(node);
        }

        public override void PostWalk(SelectStatement node)
        {
            base.PostWalk(node);
        }
        #endregion
    }
}
